package com.ztesoft.huangfu.learning.springboot.es.service.impl;

import com.ztesoft.huangfu.learning.springboot.es.document.StudentDoc;
import com.ztesoft.huangfu.learning.springboot.es.page.Page;
import com.ztesoft.huangfu.learning.springboot.es.service.EsComplexSearchSearch;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.QueryStringQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.data.querydsl.QPageRequest;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created with IntelliJ IDEA.
 * Author: huangfu.daohui
 * Date: 2018/12/20
 * Time: 14:29
 * Version: 1.0
 * Description:基于ElasticsearchTemplate的复合操作
 * 关键字
 * 分页
 * 高亮
 * 排序
 * 字段
 **/
@Service
public class EsComplexSearchSearchImpl implements EsComplexSearchSearch<StudentDoc> {

    private Logger log = LoggerFactory.getLogger(getClass());

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;


    /**
     * 根据关键字查询
     *
     * @param keyword
     * @param clazz
     * @return
     */
    @Override
    public List<StudentDoc> query(String keyword, Class<StudentDoc> clazz) {

//        QueryBuilder queryBuilder0 = QueryBuilders.rangeQuery("fieldName").from("fieldValue1").to("fieldValue2");
//        QueryBuilder queryBuilder0 = QueryBuilders.boolQuery().must();//文档必须完全匹配条件，相当于and

        /**
         * 排序：根据字段或者根据core
         * https://blog.csdn.net/tiansheng1225/article/details/82661577
         * FieldSortBuilder sort = SortBuilders.fieldSort("commentSize").order(SortOrder.DESC);
         */
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(new QueryStringQueryBuilder(keyword))
                .withSort(SortBuilders.scoreSort().order(SortOrder.DESC)) //设置排序
                /**
                 * 设置查询条件（可指定字段，设置查询所有、全部匹配、模糊、范围等等）
                 */
//                .withQuery(QueryBuilders.matchAllQuery()).withFields()
//                .withFilter(QueryBuilders.boolQuery().filter())   //设置过滤
                .build();
        return elasticsearchTemplate.queryForList(searchQuery, clazz);
    }

    /**
     * 分页查询
     *
     * @param keyword
     * @param clazz
     * @param page
     * @param size
     * @return
     */
    public List<StudentDoc> queryByPage(String keyword, Class<StudentDoc> clazz, int page, int size) {
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(new QueryStringQueryBuilder(keyword)) // 查询条件
                .withPageable(new QPageRequest(page, size)) // 分页
                .withSort(SortBuilders.scoreSort().order(SortOrder.DESC)) //排序
                .build();
        return elasticsearchTemplate.queryForList(searchQuery, clazz);
    }

    /**
     * 高亮分页查询
     *
     * @param pageNo     当前页
     * @param pageSize   每页显示的总条数
     * @param keyword    关键字
     * @param indexName  索引库
     * @param fieldNames 搜索的字段
     * @return
     */
    @Override
    public Page<Map<String, Object>> queryHitByPage(int pageNo, int pageSize, String keyword, String indexName, String... fieldNames) {

        // 构造查询条件,使用标准分词器.
        QueryBuilder matchQuery = createQueryBuilder(keyword, fieldNames);

        // 设置高亮,使用默认的highlighter高亮器
        HighlightBuilder highlightBuilder = createHighlightBuilder(fieldNames);

        // 设置查询字段
        SearchResponse response = elasticsearchTemplate
                .getClient()
                .prepareSearch(indexName)
                .highlighter(highlightBuilder)
                .setQuery(matchQuery)
                .setFrom((pageNo - 1) * pageSize)
                .setSize(pageNo * pageSize)
                .get();
        // 返回搜索结果
        SearchHits hits = response.getHits();

        //返回总数
        Long totalCount = hits.getTotalHits();
        // 设置查询字段
        Page<Map<String, Object>> page = new Page<>(pageNo, pageSize, totalCount.intValue());
        page.setList(getHitList(hits));
        return page;
    }

    @Override
    public void deleteIndex(String indexName) {
        // 先判断indexName是否存在，如果存在就删除
        if (elasticsearchTemplate.indexExists(indexName)) {
            elasticsearchTemplate.deleteIndex(indexName);
        }
    }

    /**
     * 构建查询条件
     *
     * @param keyword
     * @param fieldNames
     * @return
     */
    private QueryBuilder createQueryBuilder(String keyword, String... fieldNames) {
        // 构造查询条件,使用标准分词器.
        return QueryBuilders.multiMatchQuery(keyword, fieldNames)   // matchQuery(),单字段搜索
//                .analyzer("ik_max_word") ik分词要安装
                .operator(Operator.OR);
    }

    /**
     * 构建高亮器
     *
     * @param fieldNames
     * @return
     */
    private HighlightBuilder createHighlightBuilder(String... fieldNames) {
        // 设置高亮,使用默认的highlighter高亮器
        HighlightBuilder highlightBuilder = new HighlightBuilder()
                // .field("productName")
                .preTags("<span style='color:red'>")
                .postTags("</span>");

        // 设置高亮字段
        for (String fieldName : fieldNames) {
            highlightBuilder.field(fieldName);
        }

        return highlightBuilder;
    }

    /**
     * 处理高亮结果集
     *
     * @param hits
     * @return
     */
    private List<Map<String, Object>> getHitList(SearchHits hits) {
        List<Map<String, Object>> list = new ArrayList<>();
        Map<String, Object> map;
        for (SearchHit searchHit : hits) {
            map = new HashMap<>();
            // 处理源数据
            map.put("source", searchHit.getSourceAsMap());
            // 处理高亮数据
            Map<String, Object> hitMap = new HashMap<>();
            searchHit.getHighlightFields().forEach((k, v) -> {
                String hight = "";
                for (Text text : v.getFragments()) {
                    hight += text.string();
                }
                hitMap.put(v.getName(), hight);
            });
            map.put("highlight", hitMap);
            list.add(map);
        }
        return list;
    }
}
